/**
 * Universita' degli Studi di Palermo, 2010-11, License GNU GPL 3 (http://www.gnu.org/licenses/gpl.html)
 */
package org.unipa.botplatform;

import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.Hashtable;
import java.util.InvalidPropertiesFormatException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import org.jdom.JDOMException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.unipa.botplatform.bot.BotContext;
import org.unipa.botplatform.bot.BotParser;
import org.unipa.botplatform.model.Bot;
import org.unipa.botplatform.model.Planner;
import org.unipa.botplatform.model.Session;
import org.unipa.botplatform.util.FileManager;

/**
 * Core of the Corpus Callosum. 
 * 
 * The Core loads the settings and instantiates the bots and their context. 
 * The Core will maintain a pool of bots according the description bots file, and it can return 
 * a new interaction <code>Session</code> for any of this bots.
 * 
 * @author Giuseppe Miceli
 *
 */
public class Core {

	/** Basic settings of the core, including the path of any default file and other default values. */
	private CoreSettings settings;

	/** Spring ApplicationContext used to load the beans */
	private ApplicationContext ctx;

	/** Specific context beans of every bot */
	private Map<Bot, BotContext> bots = Collections.synchronizedMap(new Hashtable<Bot, BotContext>());

	/** Active dialogue sessions */
	private List<Session> activeSessions = new Vector<Session>();

	/**
	 * The constructor must indicate the path of the basic properties file (typically "/conf/core.xml").
	 * @param coreSettingsPath Path of the properties file.
	 */
	public Core(String coreSettingsPath) throws InvalidPropertiesFormatException, IOException, JDOMException{
		URL coreSettingsURL = FileManager.getValidURL(coreSettingsPath);
		settings = new CoreSettings(coreSettingsURL);		
		init();
	}

	/**
	 * The constructor must indicate the path of the basic properties file (typically an url pointing to "/conf/core.xml").
	 * @param coreSettingsURL URL of the properties file.
	 */
	public Core(URL coreSettingsURL) throws InvalidPropertiesFormatException, IOException, JDOMException{
		settings = new CoreSettings(coreSettingsURL);		
		init();
	}

	/**
	 * 
	 * @param session
	 * @return
	 */
	private Session addSession(Session session){
		activeSessions.add(session);
		return session;
	}

	/** 
	 * @return A <code>Bot</code> if any, in undefined order, <code>null</code> otherwise.
	 */
	public Bot getABot(){		
		if (bots.isEmpty()) return null;
		return bots.keySet().iterator().next();
	}

	/**
	 * @return the activeSessions
	 */
	public List<Session> getActiveSessions() {
		return activeSessions;
	}

	/** 
	 * @param name The name of the bot.
	 * @return The <code>Bot</code> with the given name, if exists, <code>null</code> otherwise.
	 */
	public Bot getBot(String name){
		for (Bot bot : bots.keySet()){
			if (bot.getName().equals(name)) return bot;
		}
		return null;
	}

	public Set<String> getBotsNames(){
		Set<String> names = Collections.synchronizedSet(new LinkedHashSet<String>(bots.keySet().size()));
		for (Bot bot:bots.keySet()){
			names.add(bot.getName());
		}
		return names;
	}
	
	/**
	 * Initialization method. It sets the ApplicationContext and the bots.
	 */
	private void init() throws JDOMException, IOException{
		// The beans file is loaded
		URL beansPath = this.settings.beansURL;
		this.ctx = new FileSystemXmlApplicationContext(beansPath.getPath());

		/* The bots xml parser is loaded and used to parse and create a map <Bot name, Beans>.
		 * The beans must exists in the spring beans file. */		
		URL botsURL = this.settings.botsURL;
		URL botsSchemaURL = this.settings.botsSchemaURL;
		String botsSchema = this.settings.botsSchema;
		Map<String, BotContext> nameAndCtx = BotParser.parse(botsURL, botsSchema, botsSchemaURL, settings.defaultBotCxt);

		// Using the bot names and contexts, we created the bots
		for (String name : nameAndCtx.keySet()){	
			BotContext bCtx = nameAndCtx.get(name);
			Bot newBot = (Bot) ctx.getBean(bCtx.getBotBean());
			newBot.setName(name);
			bots.put(newBot, bCtx);
		}
	}

	/**
	 * 
	 * @return
	 */
	public Session newSession(){
		Bot bot = getABot();
		return newSession(bot);
	}

	/**
	 * @param bot The bot to use in the session returned.
	 * @return A new <code>Session</code> object.
	 */
	private Session newSession(Bot bot){
		BotContext bCtx = bots.get(bot);
		Session session = (Session) ctx.getBean(bCtx.getSessionBean());
		Planner planner = (Planner) ctx.getBean(bCtx.getPlannerBean());

		session.configure(bot, planner);

		return addSession(session);	
	}

	/**
	 * 
	 * @param botName The name of the bot to use in the session returned.
	 * @return A new <code>Session</code> object.
	 * @throws NoSuchBotException 
	 */
	public Session newSession(String botName){
		Bot bot = getBot(botName);
		return newSession(bot);
	}
	
	public Boolean closeSession(Session session){
		if (this.activeSessions.remove(session)) {
			session = null;
			return true;
		} else {
			return false;
		}
	}
}
